#include <stdio.h>	/* for NULL and printf() */
#include <signal.h>	/* for signal handling */
#include <setjmp.h>	/* for setjmp and longjmp */
#include <ctype.h>	/* for isgraph */
#include <string.h>	/* for strchr */
#include <math.h>	/* for sin(), cos(), atan() and fabs() */
#include "math.h"	/* for Log(), Log10(), Exp(), Sqrt() and integer() */

#include "hoc.h"	/* for Symbol_t */
#include "code.h"	/* for Inst_t, Datum_t and Virtual Op-Codes */
#include "dump.h"	/* for trace */
#include "y.tab.h"	/* for parser tokens */

/* Define an extra Token types for follow */
#define OPCODE	0
#define END		1

/* Define karnal knowladge of the Virtual Machine */
extern Datum_t *ptr_stack;
extern Datum_t stack[];
extern Inst_t *ptr_local;
extern Symbol_t* symlist;

/* Define karnal knowladge of trace mode globals */
extern int trace_mode;
extern int trace_indent;
extern int trace_nostack;

/* Op-Code Lookup table */
struct {
	Inst_t	opcode;
	char	*name;
} opcodes[] = {
	{ eval,			"eval"		},
	{ assign,		"assign"	},
	{ bltin,		"bltin"		},
	{ print,		"print"		},
	{ varpush,		"varpush"	},
	{ constpush,	"constpush"	},
	{ drop,			"drop"		},
	{ hoc_add,		"add"		},
	{ hoc_sub,		"sub"		},
	{ hoc_mul,		"mul"		},
	{ hoc_div,		"div"		},
	{ hoc_mod,		"mod"		},
	{ hoc_negate,	"negate"	},
	{ hoc_power,	"power"		},
	{ hoc_gt,		"gt"		},
	{ hoc_ge,		"ge"		},
	{ hoc_lt,		"lt"		},
	{ hoc_le,		"le"		},
	{ hoc_eq,		"eq"		},
	{ hoc_ne,		"ne"		},
	{ hoc_and,		"and"		},
	{ hoc_or,		"or"		},
	{ hoc_not,		"not"		},
	{ prexpr,		"prexpr"	},
	{ ifcode,		"ifcode"	},
	{ whilecode,	"whilecode"	},
	{ call,			"call"		},
	{ argassign,	"argassign"	},
	{ procret,		"procret"	},
	{ funcret,		"funcret"	},
	{ arg,			"arg"		},
	{ varread,		"varread"	},
	{ prstr,		"prstring"	},
	{ trace,		"trace"		},
	{ NULL,			NULL		}
};

const char *findopcode(Inst_t opcode) {
	int i = 0;
	while ( opcodes[i].opcode != STOP ) {
		if ( opcodes[i].opcode == opcode ) {
			return opcodes[i].name;
		}
		++i;
	}
	return NULL;
}

/* Bulit-In Functions Lookup Table */
struct {
	char*	ptr_cname;
	double	(*ptr_dfunc)(double);
} builtins[] = {
	{"sin",	  sin},
	{"cos",	  cos},
	{"atan",  atan},
	{"log",	  Log},		/* Check Args */
	{"log10", Log10},	/* Check Args */
	{"exp",	  Exp},		/* Check Args */
	{"sqrt",  Sqrt},	/* Check Args */
	{"int",	  integer},	/* Check Args */
	{"abs",	  fabs},
	{NULL,	  NULL}
};

const char *findbltin(double (*ptr_dfunc)(double)) {
	int i = 0;
	while ( builtins[i].ptr_cname != NULL ) {
		if ( builtins[i].ptr_dfunc == ptr_dfunc ) {
			return builtins[i].ptr_cname;
		}
		++i;
	}
	return NULL;
}


/* Follow-on Lookup Table */
struct {
	Inst_t	inst;
	int		itoken;
} followon[] = {
	{ bltin,		BLTIN	},
	{ varpush,		VAR		},
	{ constpush,	NUMBER	},
	{ ifcode,		IF		},	/* Then, Else, Next */
	{ whilecode,	WHILE	},	/* Body, Next */
	{ call,			PROC	},	/* Symbol, nargs */
	{ argassign,	ARG		},
	{ arg,			ARG		},
	{ procret,		RETURN	},
	{ funcret,		RETURN	},
	{ varread,		VAR		},
	{ prstr,		STRING	},
	{ NULL,			0 }
};

int findfollow(Inst_t inst) {
	int i = 0;
	while ( followon[i].inst != NULL ) {
		if ( followon[i].inst == inst ) {
			return followon[i].itoken;
		}
		++i;
	}
	return OPCODE;
}

/* Find a Function / Procedure with this address */
Symbol_t *findproc(Inst_t *ptr_code) {
	Symbol_t *ptr_sym = symlist;

	while ( ptr_sym != NULL ) {
		if ( ptr_sym->itype == FUNCTION
		  || ptr_sym->itype == PROCEDURE ) {
			if ( ptr_sym->u.defn == ptr_code) {
				break;
			}
		  }
		ptr_sym = ptr_sym->ptr_next;
	}
	return ptr_sym;
}

/* Print the current line number and indent */
void printLN(int pc, int indent) {
	int i;
	if ( pc >= 0 ) {
		printf("%4d:  ", pc);
	} else {
		++indent;
	}
	for(i = 0; i < indent; ++i) {
		printf("    ");
	}
}

/* Calculate the line number from the memory address */
int getlineno(Inst_t ptr) {
	return ( ((int)ptr - (int)prog) / sizeof(Inst_t) );
}

/* Escape the control character */
char escape(char cchar) {
	static char transtab[] = "\bb\ff\nn\rr\tt";
	if ( strchr(transtab, cchar) ) {
		return strchr(transtab, cchar)[1];
	} else {
		return cchar + 'A';
	}
}

void dump(int pc, int indent) {
	int follow = OPCODE;
	const char *ptr_string;
	const char *ptr_cstruct = NULL;
	char *ptr_char;
	Symbol_t *ptr_sym;
	int first, last;

	while ( prog[pc] != STOP && follow != END ) {
		if ( follow != RETURN ) {
			printLN(pc, indent);
		}
		ptr_sym = (Symbol_t *)prog[pc++];

		/* Type of instruction */
		switch (follow) {
		case BLTIN: /* Built-In */
			ptr_string = findbltin((double (*)(double))ptr_sym);
			if ( ptr_string ) {
				printf("  %s()\n", ptr_string);
			} else {
				printf("  Unknown Built-In Function: %p\n", ptr_sym);
			}
			follow = OPCODE;
			break;

		case VAR: /* Variable */
			printf("  %s\n", ptr_sym->ptr_cname);
			follow = OPCODE;
			break;

		case NUMBER: /* Constant */
			printf("  %.8g\n", ptr_sym->u.dval);
			follow = OPCODE;
			break;

		case IF: /* If Statement */
			--pc;
			/* Tell the user what we are up to */
			first = getlineno(prog[pc]);
			printf("  Condition: %d - %d\n", pc+3, first - 1);

			printLN(pc+1, indent);
			if ( prog[pc+1] == STOP ) {
				last = getlineno(prog[pc+2]);
			} else {
				last = getlineno(prog[pc+1]);
			}
			printf("  Then: %d - %d\n", first, last - 1);

			printLN(pc+2, indent);
			printf("  Else: ");
			if ( prog[pc+1] == STOP ) {
				printf("None\n");
			} else {
				first = last;
				last = getlineno(prog[pc+2]);
				printf("%d - %d\n", first, last - 1);
			}

			/* The condition */
			printLN(-1, ++indent);
			printf(" Condition:\n");
			dump(pc+3, indent);

			/* True Part */
  			printLN(-1, indent);
			printf(" True Part:\n");
			first = getlineno(prog[pc]);
			dump(first, indent);

			/* Else Part */
			if ( prog[pc+1] != 0 ) {
				printLN(-1,indent);
				printf(" Else Part:\n");
				first = getlineno(prog[pc+1]);
				dump(first, indent);
			}

			--indent;
			ptr_cstruct = "if";
			pc = getlineno(prog[pc+2]);
			follow = OPCODE;
			break;

		case WHILE: /* While Statment */
			--pc;
			/* Tell the user what we are doing */
			first = getlineno(prog[pc]);
			printf("  Condition: %d - %d\n", pc+2, first - 1 );

			printLN(pc+1, indent);
			last = getlineno(prog[pc+1]);
			printf("  Loop Body: %d - %d\n", first, last - 1 );

			/* The condition */
			printLN(-1, ++indent);
			printf(" Condition:\n");
			dump(pc+2, indent);

			/* The Loop Body */
			printLN(-1, indent);
			printf(" Loop Body:\n");
 			dump(first, indent--);

			ptr_cstruct = "while";
 			pc = last;
			follow = OPCODE;
			break;

		case PROC: /* Function or Procedure Call */
			printf("  %s\n", ptr_sym->ptr_cname);

			printLN(pc, indent);
			follow = (int)prog[pc++];
			switch (follow) {
			case 0: printf("  No Arguments\n");	break;
			case 1: printf("  1 Argument\n");	break;
			default:printf("  %d Aguments\n", follow);
			}
			follow = OPCODE;
			break;

		case ARG: /* Paramater Reference */
			printf("  %d\n", (int)ptr_sym);
			follow = OPCODE;
			break;

		case RETURN:
			--pc;
			follow = END;
			if ( prog + pc < ptr_base
			  && findproc(prog + pc) == NULL ) {
				follow = OPCODE;
			}
			break;

		case STRING: /* String */
			ptr_char = (char *)ptr_sym;
			printf("  \"");
			while ( *ptr_char != '\0' ) {
				follow = *ptr_char++;
				if ( iscntrl(follow) ) {
					printf("\\%c", escape(follow));
				} else {
					printf("%c", follow);
				}
			}
			printf("\"\n");
			follow = OPCODE;
			break;

		default: /* Op-Code */
			ptr_string = findopcode((Inst_t)ptr_sym);
			if ( ptr_string ) {
				printf("%s\n", ptr_string);
			} else {
				printf("Unknown Op-code: %p\n", ptr_sym);
			}
			follow = findfollow((Inst_t)ptr_sym);
		}
		if ( ptr_cstruct != NULL ) {
			printLN(-1, indent);
			printf("   end-%s\n", ptr_cstruct);
			ptr_cstruct = NULL;
		}
	}
}


/* Dump the most recently comilied line */
void dump_last() {
	dump(getlineno((Inst_t)ptr_base), 0);
}

/* Dump a single procedure or function */
void dump_proc(Symbol_t *ptr_sym) {
	char *ptr_ctype = NULL;

	switch (ptr_sym->itype) {
	case FUNCTION:	ptr_ctype = "Function";  break;
	case PROCEDURE:	ptr_ctype = "Procedure"; break;
	}

	if ( ptr_ctype != NULL ) {
		printf("\n%s: %s()\n", ptr_ctype, ptr_sym->ptr_cname);
		dump(getlineno((Inst_t)ptr_sym->u.defn), 0);
	} else {
		execerror("Can't dump", ptr_sym->ptr_cname);
	}
}

/* Dump all of the compiled memory */
void dump_all() {
	Inst_t *ptr_proc = prog;
	Symbol_t *ptr_sym;

	while ( ptr_proc < ptr_base ) {
		ptr_sym = findproc(ptr_proc);
		if ( ptr_sym != NULL ) {
			dump_proc(ptr_sym);
		}
		++ptr_proc;
	}
}



/* Routines to support the trace mode */

/* Enable trace mode */
void trace() {
	int old_trace_mode = trace_mode;	/* Save curernt Trace mode */
	trace_mode = 1;						/* Enable Trace mode */
	trace_nostack = 1;					/* Show Stack Trace */
	execute(ptr_local);					/* Trace the code */
	trace_mode = old_trace_mode;		/* Restore old Trace mode */
}

/* Display the current "line number" and op-code */
void printopcode() {
		printLN(getlineno((Inst_t)ptr_local), trace_indent - 1);
		printf("%-12s ", findopcode(*ptr_local));
}

/* Catch a Segmentation Falut */

/* The showstack buffer is used to pass control from the signal handler
   back to the printstack() routine. */

jmp_buf showstack;

/* We display the name of the current stack item, assuming it is a
   symbol. If it is not a symbol, or the name is blank (it is a constant
   value) this will cause a segmentation fault (segv). We catch this
   fault here and return a true (1) to the setjmp function in the
   printstack routine.

   Note that the default signal handler is restored when our handler
   is called, so we have to reset it within this function.
*/

#ifdef __cplusplus
extern "C" {
	void segvcatch(int value) {
		signal(SIGSEGV, segvcatch);		/* Reset SEGV Signal Handler */
		longjmp(showstack, 1);			/* Return to setjmp with a true */
	}
}
#else
	void segvcatch(int value) {
		signal(SIGSEGV, segvcatch);
		longjmp(showstack, 1);
	}
#endif

/* Display the current data stack */
void printstack() {
	Datum_t *ptr_curr = stack;

	/* Override the default segmentation fault handler */
	signal(SIGSEGV, segvcatch);
	printf(" (");

	/* Display each item on the stack in turn */
	while ( ptr_curr != ptr_stack) {
		/* setjmp will normally return a false (0), except when a
		   segv fault occures when our handler causes it to return
		   a true (1) */
		if ( setjmp(showstack) ) {
			/* A segv fault must have occured */
			/* We cant have a name for the symbol so it must be a value */
			/* Add type checking here*/
			printf(" %.8g", ptr_curr->u.dval);
		} else {
			/* Print the name of the symbol */
			printf(" %s", ptr_curr->u.ptr_sym->ptr_cname);
		}
		++ptr_curr;
	}
	printf(" )");

	/* Reset segv fault handler back to the default */
	signal(SIGSEGV, SIG_DFL);
}

